/*
 * Copyright 2012 Alexandre Julliard
 * Copyright 2025 Rémi Bernon for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#if 0
#pragma makedep unix
#endif

#include "config.h"

#include <assert.h>
#include <pthread.h>
#include <dlfcn.h>

#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "ntgdi_private.h"
#include "win32u_private.h"
#include "ntuser_private.h"

#include "wine/opengl_driver.h"

#include "dibdrv/dibdrv.h"

WINE_DEFAULT_DEBUG_CHANNEL(wgl);

struct wgl_pbuffer
{
    struct opengl_drawable *drawable;

    HDC hdc;
    GLsizei width;
    GLsizei height;
    GLenum texture_format;
    GLenum texture_target;
    GLint mipmap_level;
    GLenum cube_face;
};

static const struct opengl_driver_funcs nulldrv_funcs, *driver_funcs = &nulldrv_funcs;
static const struct opengl_funcs *default_funcs; /* default GL function table from opengl32 */
static struct egl_platform display_egl;
static struct opengl_funcs display_funcs;

static struct wgl_pixel_format *pixel_formats;
static UINT formats_count, onscreen_count;
static char wgl_extensions[4096];

static BOOL has_extension( const char *list, const char *ext )
{
    size_t len = strlen( ext );
    const char *cur = list;

    while (cur && (cur = strstr( cur, ext )))
    {
        if ((!cur[len] || cur[len] == ' ') && (cur == list || cur[-1] == ' ')) return TRUE;
        cur = strchr( cur, ' ' );
    }

    return FALSE;
}

static void dump_extensions( const char *list )
{
    const char *start, *end, *ptr;

    for (start = end = ptr = list; ptr; ptr = strchr( ptr + 1, ' ' ))
    {
        if (ptr - start <= 128) end = ptr;
        else
        {
            TRACE( "%.*s\n", (int)(end - start), start );
            start = end + 1;
        }
    }

    TRACE( "%s\n", start );
}

static void register_extension( char *list, size_t size, const char *name )
{
    if (!has_extension( list, name ))
    {
        size_t len = strlen( list );
        assert( size - len >= strlen( name ) + 1 );
        if (*list) strcat( list + len, " " );
        strcat( list + len, name );
    }
}

void *opengl_drawable_create( UINT size, const struct opengl_drawable_funcs *funcs, int format, struct client_surface *client )
{
    struct opengl_drawable *drawable;

    if (!(drawable = calloc( 1, size ))) return NULL;
    drawable->funcs = funcs;
    drawable->ref = 1;

    drawable->format = format;
    drawable->interval = INT_MIN;
    drawable->doublebuffer = !!(pixel_formats[format - 1].pfd.dwFlags & PFD_DOUBLEBUFFER);
    drawable->stereo = !!(pixel_formats[format - 1].pfd.dwFlags & PFD_STEREO);
    if ((drawable->client = client)) client_surface_add_ref( client );

    TRACE( "created %s\n", debugstr_opengl_drawable( drawable ) );
    return drawable;
}

void opengl_drawable_add_ref( struct opengl_drawable *drawable )
{
    ULONG ref = InterlockedIncrement( &drawable->ref );
    TRACE( "%s increasing refcount to %u\n", debugstr_opengl_drawable( drawable ), ref );
}

void opengl_drawable_release( struct opengl_drawable *drawable )
{
    ULONG ref = InterlockedDecrement( &drawable->ref );
    TRACE( "%s decreasing refcount to %u\n", debugstr_opengl_drawable( drawable ), ref );

    if (!ref)
    {
        const struct opengl_funcs *funcs = &display_funcs;
        const struct egl_platform *egl = &display_egl;

        drawable->funcs->destroy( drawable );
        if (drawable->surface) funcs->p_eglDestroySurface( egl->display, drawable->surface );
        if (drawable->client) client_surface_release( drawable->client );
        free( drawable );
    }
}

static void opengl_drawable_flush( struct opengl_drawable *drawable, int interval, UINT flags )
{
    if (!drawable->client) return;

    if (InterlockedCompareExchange( &drawable->client->updated, 0, 1 )) flags |= GL_FLUSH_UPDATED;
    if (interval != drawable->interval)
    {
        drawable->interval = interval;
        flags = GL_FLUSH_INTERVAL;
    }

    if (flags || InterlockedCompareExchange( &drawable->client->offscreen, 0, 0 ))
        drawable->funcs->flush( drawable, flags );
}

#ifdef SONAME_LIBEGL

struct framebuffer_surface
{
    struct opengl_drawable base;
};

static GLenum color_format_from_pfd( const struct wgl_pixel_format *desc )
{
    TRACE( "format type %u bits %u/%u/%u/%u\n", desc->pixel_type, desc->pfd.cRedBits,
           desc->pfd.cGreenBits, desc->pfd.cBlueBits, desc->pfd.cAlphaBits );

    if (desc->pixel_type == WGL_TYPE_RGBA_FLOAT_ARB)
    {
        if (desc->pfd.cAlphaBits == 32) return GL_RGBA32F;
        if (desc->pfd.cAlphaBits == 16) return GL_RGBA16F;
        if (desc->pfd.cBlueBits == 32) return GL_RGB32F;
        if (desc->pfd.cBlueBits == 16) return GL_RGB16F;
        if (desc->pfd.cGreenBits == 32) return GL_RG32F;
        if (desc->pfd.cGreenBits == 16) return GL_RG16F;
        if (desc->pfd.cRedBits == 32) return GL_R32F;
        if (desc->pfd.cRedBits == 16) return GL_R16F;
    }
    else
    {
        if (desc->pfd.cBlueBits == 10 && desc->pfd.cGreenBits == 10 &&
            desc->pfd.cRedBits == 10 && desc->pfd.cAlphaBits == 2)
            return GL_RGB10_A2;
        if (desc->pfd.cAlphaBits == 32) return GL_RGBA32UI;
        if (desc->pfd.cAlphaBits == 16) return GL_RGBA16;
        if (desc->pfd.cAlphaBits == 8) return GL_RGBA8;
        if (desc->pfd.cAlphaBits == 4) return GL_RGBA4;
        if (desc->pfd.cBlueBits == 32) return GL_RGB32UI;
        if (desc->pfd.cBlueBits == 16) return GL_RGB16;
        if (desc->pfd.cBlueBits == 8) return GL_RGB8;
        if (desc->pfd.cBlueBits == 4) return GL_RGB4;
        if (desc->pfd.cGreenBits == 32) return GL_RG32UI;
        if (desc->pfd.cGreenBits == 16) return GL_RG16;
        if (desc->pfd.cGreenBits == 8) return GL_RG8;
        if (desc->pfd.cRedBits == 32) return GL_R32UI;
        if (desc->pfd.cRedBits == 16) return GL_R16;
        if (desc->pfd.cRedBits == 8) return GL_R8;
    }

    FIXME( "Unsupported format type %u bits %u/%u/%u/%u\n", desc->pixel_type, desc->pfd.cRedBits,
           desc->pfd.cGreenBits, desc->pfd.cBlueBits, desc->pfd.cAlphaBits );
    return 0;
}

static GLenum depth_format_from_pfd( const struct wgl_pixel_format *desc )
{
    TRACE( "format bits %u/%u\n", desc->pfd.cStencilBits, desc->pfd.cDepthBits );

    if (desc->pfd.cStencilBits)
    {
        if (desc->pfd.cDepthBits == 32) return GL_DEPTH32F_STENCIL8;
        if (desc->pfd.cDepthBits == 24) return GL_DEPTH24_STENCIL8;
    }
    else
    {
        if (desc->pfd.cDepthBits == 32) return GL_DEPTH_COMPONENT32;
        if (desc->pfd.cDepthBits == 24) return GL_DEPTH_COMPONENT24;
        if (desc->pfd.cDepthBits == 16) return GL_DEPTH_COMPONENT16;
    }

    FIXME( "Unsupported format bits %u/%u\n", desc->pfd.cStencilBits, desc->pfd.cDepthBits );
    return 0;
}

static GLuint create_framebuffer( struct opengl_drawable *drawable, const struct wgl_pixel_format *desc )
{
    const struct opengl_funcs *funcs = &display_funcs;
    GLuint count = 1, fbo, name;

    if (drawable->doublebuffer) count *= 2;
    if (drawable->stereo) count *= 2;

    funcs->p_glCreateFramebuffers( 1, &fbo );

    for (GLuint i = 0; i < count; i++)
    {
        funcs->p_glCreateRenderbuffers( 1, &name );
        funcs->p_glNamedFramebufferRenderbuffer( fbo, GL_COLOR_ATTACHMENT0 + i, GL_RENDERBUFFER, name );
        TRACE( "drawable %p/%u created color buffer %#x/%u\n", drawable, fbo, GL_COLOR_ATTACHMENT0 + i, name );
    }

    if (desc->pfd.cDepthBits)
    {
        funcs->p_glCreateRenderbuffers( 1, &name );
        funcs->p_glNamedFramebufferRenderbuffer( fbo, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, name );
        if (desc->pfd.cStencilBits) funcs->p_glNamedFramebufferRenderbuffer( fbo, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, name );
        TRACE( "drawable %p/%u created depth buffer %u\n", drawable, fbo, name );
    }

    funcs->p_glNamedFramebufferDrawBuffer( fbo, GL_COLOR_ATTACHMENT0 );
    funcs->p_glNamedFramebufferReadBuffer( fbo, drawable->doublebuffer ? GL_COLOR_ATTACHMENT1 : GL_COLOR_ATTACHMENT0 );
    TRACE( "drawable %p created framebuffer %u\n", drawable, fbo );

    return fbo;
}

static void resize_framebuffer( struct opengl_drawable *drawable, const struct wgl_pixel_format *desc, GLuint fbo,
                                int width, int height )
{
    const struct opengl_funcs *funcs = &display_funcs;
    GLuint count = 1, name;
    GLenum ret;

    if (drawable->doublebuffer) count *= 2;
    if (drawable->stereo) count *= 2;

    for (GLuint i = 0; i < count; i++)
    {
        funcs->p_glGetNamedFramebufferAttachmentParameteriv( fbo, GL_COLOR_ATTACHMENT0 + i, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&name );
        funcs->p_glNamedRenderbufferStorageMultisample( name, desc->samples, color_format_from_pfd( desc ), width, height );
        TRACE( "drawable %p/%u resized color buffer %#x/%u to %d,%d\n", drawable, fbo, GL_COLOR_ATTACHMENT0 + i, name, width, height );
    }

    if (desc->pfd.cDepthBits)
    {
        funcs->p_glGetNamedFramebufferAttachmentParameteriv( fbo, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&name );
        funcs->p_glNamedRenderbufferStorageMultisample( name, desc->samples, depth_format_from_pfd( desc ), width, height );
        TRACE( "drawable %p/%u resized depth buffer %u to %d,%d\n", drawable, fbo, name, width, height );
    }

    ret = funcs->p_glCheckNamedFramebufferStatus( fbo, GL_FRAMEBUFFER );
    if (ret != GL_FRAMEBUFFER_COMPLETE) WARN( "glCheckNamedFramebufferStatus returned %#x\n", ret );
    TRACE( "drawable %p/%u resized buffers to %d,%d\n", drawable, fbo, width, height );
}

static void destroy_framebuffer( struct opengl_drawable *drawable, const struct wgl_pixel_format *desc, GLuint fbo )
{
    const struct opengl_funcs *funcs = &display_funcs;
    GLuint count = 1, name;

    if (drawable->doublebuffer) count *= 2;
    if (drawable->stereo) count *= 2;

    for (GLuint i = 0; i < count; i++)
    {
        funcs->p_glGetNamedFramebufferAttachmentParameteriv( fbo, GL_COLOR_ATTACHMENT0 + i, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&name );
        funcs->p_glDeleteRenderbuffers( 1, &name );
        TRACE( "drawable %p/%u destroyed color buffer %#x/%u\n", drawable, fbo, GL_COLOR_ATTACHMENT0 + i, name );
    }

    if (desc->pfd.cDepthBits)
    {
        funcs->p_glGetNamedFramebufferAttachmentParameteriv( fbo, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&name );
        funcs->p_glDeleteRenderbuffers( 1, &name );
        TRACE( "drawable %p/%u destroyed depth buffer %u\n", drawable, fbo, name );
    }

    funcs->p_glDeleteFramebuffers( 1, &fbo );
    TRACE( "drawable %p destroyed framebuffer %u\n", drawable, fbo );
}

static void framebuffer_surface_destroy( struct opengl_drawable *drawable )
{
    TRACE( "%s\n", debugstr_opengl_drawable( drawable ) );
}

static void framebuffer_surface_flush( struct opengl_drawable *drawable, UINT flags )
{
    struct wgl_pixel_format draw_desc = pixel_formats[drawable->format - 1], read_desc = draw_desc;
    RECT rect;

    TRACE( "%s, flags %#x\n", debugstr_opengl_drawable( drawable ), flags );

    NtUserGetClientRect( drawable->client->hwnd, &rect, NtUserGetDpiForWindow( drawable->client->hwnd ) );
    if (!rect.right) rect.right = 1;
    if (!rect.bottom) rect.bottom = 1;

    read_desc.samples = read_desc.sample_buffers = 0;

    if (flags & GL_FLUSH_WAS_CURRENT)
    {
        if (drawable->draw_fbo != drawable->read_fbo)
        {
            destroy_framebuffer( drawable, &draw_desc, drawable->draw_fbo );
            drawable->draw_fbo = 0;
        }
        destroy_framebuffer( drawable, &read_desc, drawable->read_fbo );
        drawable->read_fbo = 0;
    }

    if (flags & GL_FLUSH_SET_CURRENT)
    {
        drawable->read_fbo = create_framebuffer( drawable, &read_desc );
        if (!drawable->read_fbo) ERR( "Failed to create read framebuffer object\n" );

        if (!draw_desc.sample_buffers) drawable->draw_fbo = drawable->read_fbo;
        else drawable->draw_fbo = create_framebuffer( drawable, &draw_desc );
        if (!drawable->draw_fbo) ERR( "Failed to create draw framebuffer object\n" );
    }

    if ((flags & (GL_FLUSH_UPDATED | GL_FLUSH_SET_CURRENT)) && drawable->read_fbo)
    {
        TRACE( "Resizing drawable %p/%u to %ux%u\n", drawable, drawable->read_fbo, rect.right, rect.bottom );
        resize_framebuffer( drawable, &read_desc, drawable->read_fbo, rect.right, rect.bottom );

        if (drawable->draw_fbo != drawable->read_fbo)
        {
            TRACE( "Resizing drawable %p/%u to %ux%u\n", drawable, drawable->draw_fbo, rect.right, rect.bottom );
            resize_framebuffer( drawable, &draw_desc, drawable->draw_fbo, rect.right, rect.bottom );
        }
    }
}

static BOOL framebuffer_surface_swap( struct opengl_drawable *drawable )
{
    TRACE( "%s\n", debugstr_opengl_drawable( drawable ) );
    return TRUE;
}

static const struct opengl_drawable_funcs framebuffer_surface_funcs =
{
    .destroy = framebuffer_surface_destroy,
    .flush = framebuffer_surface_flush,
    .swap = framebuffer_surface_swap,
};

static struct opengl_drawable *framebuffer_surface_create( int format, struct client_surface *client )
{
    struct framebuffer_surface *surface;
    if (!(surface = opengl_drawable_create( sizeof(*surface), &framebuffer_surface_funcs, format, client ))) return NULL;
    return &surface->base;
}

static const struct opengl_drawable_funcs egldrv_pbuffer_funcs;

static inline EGLConfig egl_config_for_format( const struct egl_platform *egl, int format )
{
    assert(format > 0 && format <= 2 * egl->config_count);
    if (format <= egl->config_count) return egl->configs[format - 1];
    return egl->configs[format - egl->config_count - 1];
}

static void egldrv_init_egl_platform( struct egl_platform *platform )
{
    platform->type = EGL_PLATFORM_SURFACELESS_MESA;
    platform->native_display = 0;
}

static void *egldrv_get_proc_address( const char *name )
{
    return display_funcs.p_eglGetProcAddress( name );
}

static UINT egldrv_init_pixel_formats( UINT *onscreen_count )
{
    const struct opengl_funcs *funcs = &display_funcs;
    struct egl_platform *egl = &display_egl;
    EGLConfig *configs;
    EGLint i, j, render, count;

    funcs->p_eglGetConfigs( egl->display, NULL, 0, &count );
    if (!(configs = malloc( count * sizeof(*configs) ))) return 0;
    if (!funcs->p_eglGetConfigs( egl->display, configs, count, &count ) || !count)
    {
        ERR( "Failed to get any configs from eglChooseConfig\n" );
        free( configs );
        return 0;
    }

    for (i = 0, j = 0; i < count; i++)
    {
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_RENDERABLE_TYPE, &render );
        if (render & EGL_OPENGL_BIT) configs[j++] = configs[i];
    }
    count = j;

    if (TRACE_ON(wgl)) for (i = 0; i < count; i++)
    {
        EGLint id, type, visual_id, native, color, r, g, b, a, d, s;
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_NATIVE_VISUAL_ID, &visual_id );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_SURFACE_TYPE, &type );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_RENDERABLE_TYPE, &render );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_CONFIG_ID, &id );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_NATIVE_RENDERABLE, &native );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_COLOR_BUFFER_TYPE, &color );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_RED_SIZE, &r );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_GREEN_SIZE, &g );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_BLUE_SIZE, &b );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_ALPHA_SIZE, &a );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_DEPTH_SIZE, &d );
        funcs->p_eglGetConfigAttrib( egl->display, configs[i], EGL_STENCIL_SIZE, &s );
        TRACE( "%u: config %d id %d type %x visual %d native %d render %x colortype %d rgba %d,%d,%d,%d depth %u stencil %d\n",
               count, i, id, type, visual_id, native, render, color, r, g, b, a, d, s );
    }

    egl->configs = configs;
    egl->config_count = count;
    *onscreen_count = count;
    return 2 * count;
}

static BOOL describe_egl_config( EGLConfig config, struct wgl_pixel_format *fmt, BOOL onscreen )
{
    const struct opengl_funcs *funcs = &display_funcs;
    struct egl_platform *egl = &display_egl;
    EGLint value, surface_type;
    PIXELFORMATDESCRIPTOR *pfd = &fmt->pfd;

    /* If we can't get basic information, there is no point continuing */
    if (!funcs->p_eglGetConfigAttrib( egl->display, config, EGL_SURFACE_TYPE, &surface_type )) return FALSE;
    if (egl->type == EGL_PLATFORM_SURFACELESS_MESA) surface_type |= EGL_WINDOW_BIT | EGL_PBUFFER_BIT;

    memset( fmt, 0, sizeof(*fmt) );
    pfd->nSize = sizeof(*pfd);
    pfd->nVersion = 1;
    pfd->dwFlags = PFD_SUPPORT_OPENGL | PFD_SUPPORT_COMPOSITION;
    if (onscreen)
    {
        pfd->dwFlags |= PFD_DOUBLEBUFFER;
        if (surface_type & EGL_WINDOW_BIT) pfd->dwFlags |= PFD_DRAW_TO_WINDOW;
    }
    pfd->iPixelType = PFD_TYPE_RGBA;
    pfd->iLayerType = PFD_MAIN_PLANE;

#define SET_ATTRIB( field, attrib )                                                                \
    value = 0;                                                                                     \
    funcs->p_eglGetConfigAttrib( egl->display, config, attrib, &value );                           \
    pfd->field = value;
#define SET_ATTRIB_ARB( field, attrib )                                                            \
    if (!funcs->p_eglGetConfigAttrib( egl->display, config, attrib, &value )) value = -1;          \
    fmt->field = value;

    /* Although the documentation describes cColorBits as excluding alpha, real
     * drivers tend to return the full pixel size, so do the same. */
    SET_ATTRIB( cColorBits, EGL_BUFFER_SIZE );
    SET_ATTRIB( cRedBits, EGL_RED_SIZE );
    SET_ATTRIB( cGreenBits, EGL_GREEN_SIZE );
    SET_ATTRIB( cBlueBits, EGL_BLUE_SIZE );
    SET_ATTRIB( cAlphaBits, EGL_ALPHA_SIZE );
    /* Although we don't get information from EGL about the component shifts
     * or the native format, the 0xARGB order is the most common. */
    pfd->cBlueShift = 0;
    pfd->cGreenShift = pfd->cBlueBits;
    pfd->cRedShift = pfd->cGreenBits + pfd->cBlueBits;
    if (!pfd->cAlphaBits) pfd->cAlphaShift = 0;
    else pfd->cAlphaShift = pfd->cRedBits + pfd->cGreenBits + pfd->cBlueBits;

    SET_ATTRIB( cDepthBits, EGL_DEPTH_SIZE );
    SET_ATTRIB( cStencilBits, EGL_STENCIL_SIZE );

    fmt->swap_method = WGL_SWAP_UNDEFINED_ARB;

    if (funcs->p_eglGetConfigAttrib( egl->display, config, EGL_TRANSPARENT_TYPE, &value ))
    {
        switch (value)
        {
        case EGL_TRANSPARENT_RGB:
            fmt->transparent = GL_TRUE;
            break;
        case EGL_NONE:
            fmt->transparent = GL_FALSE;
            break;
        default:
            ERR( "unexpected transparency type 0x%x\n", value );
            fmt->transparent = -1;
            break;
        }
    }
    else fmt->transparent = -1;

    if (!egl->has_EGL_EXT_pixel_format_float) fmt->pixel_type = WGL_TYPE_RGBA_ARB;
    else if (funcs->p_eglGetConfigAttrib( egl->display, config, EGL_COLOR_COMPONENT_TYPE_EXT, &value ))
    {
        switch (value)
        {
        case EGL_COLOR_COMPONENT_TYPE_FIXED_EXT:
            fmt->pixel_type = WGL_TYPE_RGBA_ARB;
            break;
        case EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT:
            fmt->pixel_type = WGL_TYPE_RGBA_FLOAT_ARB;
            break;
        default:
            ERR( "unexpected color component type 0x%x\n", value );
            fmt->pixel_type = -1;
            break;
        }
    }
    else fmt->pixel_type = -1;

    if (egl->force_pbuffer_formats) fmt->draw_to_pbuffer = TRUE;
    else if (surface_type & EGL_PBUFFER_BIT) fmt->draw_to_pbuffer = TRUE;
    if (fmt->draw_to_pbuffer) pfd->dwFlags |= PFD_DRAW_TO_BITMAP;

    /* Use some arbitrary but reasonable limits (4096 is also Mesa's default) */
    fmt->max_pbuffer_width = 4096;
    fmt->max_pbuffer_height = 4096;
    fmt->max_pbuffer_pixels = fmt->max_pbuffer_width * fmt->max_pbuffer_height;

    if (funcs->p_eglGetConfigAttrib( egl->display, config, EGL_TRANSPARENT_RED_VALUE, &value ))
    {
        fmt->transparent_red_value_valid = GL_TRUE;
        fmt->transparent_red_value = value;
    }
    if (funcs->p_eglGetConfigAttrib( egl->display, config, EGL_TRANSPARENT_GREEN_VALUE, &value ))
    {
        fmt->transparent_green_value_valid = GL_TRUE;
        fmt->transparent_green_value = value;
    }
    if (funcs->p_eglGetConfigAttrib( egl->display, config, EGL_TRANSPARENT_BLUE_VALUE, &value ))
    {
        fmt->transparent_blue_value_valid = GL_TRUE;
        fmt->transparent_blue_value = value;
    }
    fmt->transparent_alpha_value_valid = GL_TRUE;
    fmt->transparent_alpha_value = 0;
    fmt->transparent_index_value_valid = GL_TRUE;
    fmt->transparent_index_value = 0;

    SET_ATTRIB_ARB( sample_buffers, EGL_SAMPLE_BUFFERS );
    SET_ATTRIB_ARB( samples, EGL_SAMPLES );

    fmt->bind_to_texture_rgb = GL_TRUE;
    fmt->bind_to_texture_rgba = GL_TRUE;
    fmt->bind_to_texture_rectangle_rgb = GL_TRUE;
    fmt->bind_to_texture_rectangle_rgba = GL_TRUE;

    /* TODO: Support SRGB surfaces and enable the attribute */
    fmt->framebuffer_srgb_capable = GL_FALSE;

    fmt->float_components = GL_FALSE;

#undef SET_ATTRIB
#undef SET_ATTRIB_ARB
    return TRUE;
}

static BOOL egldrv_describe_pixel_format( int format, struct wgl_pixel_format *desc )
{
    struct egl_platform *egl = &display_egl;
    int count = egl->config_count;
    BOOL onscreen = TRUE;

    if (--format < 0 || format > 2 * count) return FALSE;
    if (format >= count) onscreen = FALSE;
    return describe_egl_config( egl->configs[format % count], desc, onscreen );
}

static const char *egldrv_init_wgl_extensions( struct opengl_funcs *funcs )
{
    return "";
}

static BOOL egldrv_surface_create( HWND hwnd, int format, struct opengl_drawable **drawable )
{
    struct client_surface *client;

    if (!(client = nulldrv_client_surface_create( hwnd ))) return FALSE;
    *drawable = framebuffer_surface_create( format, client );
    client_surface_release( client );

    return !!*drawable;
}

static BOOL egldrv_pbuffer_create( HDC hdc, int format, BOOL largest, GLenum texture_format, GLenum texture_target,
                                   GLint max_level, GLsizei *width, GLsizei *height, struct opengl_drawable **drawable )
{
    const struct opengl_funcs *funcs = &display_funcs;
    const struct egl_platform *egl = &display_egl;
    EGLint attribs[13], *attrib = attribs;
    struct opengl_drawable *gl;

    TRACE( "hdc %p, format %d, largest %u, texture_format %#x, texture_target %#x, max_level %#x, width %d, height %d, drawable %p\n",
           hdc, format, largest, texture_format, texture_target, max_level, *width, *height, drawable );

    *attrib++ = EGL_WIDTH;
    *attrib++ = *width;
    *attrib++ = EGL_HEIGHT;
    *attrib++ = *height;
    if (largest)
    {
        *attrib++ = EGL_LARGEST_PBUFFER;
        *attrib++ = 1;
    }
    switch (texture_format)
    {
    case 0: break;
    case GL_RGB:
        *attrib++ = EGL_TEXTURE_FORMAT;
        *attrib++ = EGL_TEXTURE_RGB;
        break;
    case GL_RGBA:
        *attrib++ = EGL_TEXTURE_FORMAT;
        *attrib++ = EGL_TEXTURE_RGBA;
        break;
    default:
        FIXME( "Unsupported format %#x\n", texture_format );
        *attrib++ = EGL_TEXTURE_FORMAT;
        *attrib++ = EGL_TEXTURE_RGBA;
        break;
    }
    switch (texture_target)
    {
    case 0: break;
    case GL_TEXTURE_2D:
        *attrib++ = EGL_TEXTURE_TARGET;
        *attrib++ = EGL_TEXTURE_2D;
        break;
    default:
        FIXME( "Unsupported target %#x\n", texture_target );
        *attrib++ = EGL_TEXTURE_TARGET;
        *attrib++ = EGL_TEXTURE_2D;
        break;
    }
    if (max_level)
    {
        *attrib++ = EGL_MIPMAP_TEXTURE;
        *attrib++ = GL_TRUE;
    }
    *attrib++ = EGL_NONE;

    if (!(gl = opengl_drawable_create( sizeof(*gl), &egldrv_pbuffer_funcs, format, NULL ))) return FALSE;
    if (!(gl->surface = funcs->p_eglCreatePbufferSurface( egl->display, egl_config_for_format( egl, gl->format ), attribs )))
    {
        opengl_drawable_release( gl );
        return FALSE;
    }

    funcs->p_eglQuerySurface( egl->display, gl->surface, EGL_WIDTH, width );
    funcs->p_eglQuerySurface( egl->display, gl->surface, EGL_HEIGHT, height );

    *drawable = gl;
    return TRUE;
}

static BOOL egldrv_pbuffer_updated( HDC hdc, struct opengl_drawable *drawable, GLenum cube_face, GLint mipmap_level )
{
    return GL_TRUE;
}

static UINT egldrv_pbuffer_bind( HDC hdc, struct opengl_drawable *drawable, GLenum buffer )
{
    return -1; /* use default implementation */
}

static BOOL egldrv_context_create( int format, void *share, const int *attribs, void **context )
{
    const struct opengl_funcs *funcs = &display_funcs;
    const struct egl_platform *egl = &display_egl;
    EGLint egl_attribs[16], *attribs_end = egl_attribs;

    TRACE( "format %d, share %p, attribs %p\n", format, share, attribs );

    for (; attribs && attribs[0] != 0; attribs += 2)
    {
        EGLint name;

        TRACE( "%#x %#x\n", attribs[0], attribs[1] );

        /* Find the EGL attribute names corresponding to the WGL names.
         * For all of the attributes below, the values match between the two
         * systems, so we can use them directly. */
        switch (attribs[0])
        {
        case WGL_CONTEXT_MAJOR_VERSION_ARB:
            name = EGL_CONTEXT_MAJOR_VERSION_KHR;
            break;
        case WGL_CONTEXT_MINOR_VERSION_ARB:
            name = EGL_CONTEXT_MINOR_VERSION_KHR;
            break;
        case WGL_CONTEXT_FLAGS_ARB:
            name = EGL_CONTEXT_FLAGS_KHR;
            break;
        case WGL_CONTEXT_OPENGL_NO_ERROR_ARB:
            name = EGL_CONTEXT_OPENGL_NO_ERROR_KHR;
            break;
        case WGL_CONTEXT_PROFILE_MASK_ARB:
            if (attribs[1] & WGL_CONTEXT_ES2_PROFILE_BIT_EXT)
            {
                ERR( "OpenGL ES contexts are not supported\n" );
                return FALSE;
            }
            name = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR;
            break;
        default:
            name = EGL_NONE;
            FIXME( "Unhandled attributes: %#x %#x\n", attribs[0], attribs[1] );
        }

        if (name != EGL_NONE)
        {
            EGLint *dst = egl_attribs;
            /* Check if we have already set the same attribute and replace it. */
            for (; dst != attribs_end && *dst != name; dst += 2) continue;
            /* Our context attribute array should have enough space for all the
             * attributes we support (we merge repetitions), plus EGL_NONE. */
            assert( dst - egl_attribs <= ARRAY_SIZE(egl_attribs) - 3 );
            dst[0] = name;
            dst[1] = attribs[1];
            if (dst == attribs_end) attribs_end += 2;
        }
    }
    *attribs_end = EGL_NONE;

    /* For now only OpenGL is supported. It's enough to set the API only for
     * context creation, since:
     * 1. the default API is EGL_OPENGL_ES_API
     * 2. the EGL specification says in section 3.7:
     *    > EGL_OPENGL_API and EGL_OPENGL_ES_API are interchangeable for all
     *    > purposes except eglCreateContext.
     */
    funcs->p_eglBindAPI( EGL_OPENGL_API );
    *context = funcs->p_eglCreateContext( egl->display, EGL_NO_CONFIG_KHR, share, attribs ? egl_attribs : NULL );
    TRACE( "Created context %p\n", *context );
    return TRUE;
}

static BOOL egldrv_context_destroy( void *context )
{
    const struct opengl_funcs *funcs = &display_funcs;
    const struct egl_platform *egl = &display_egl;

    funcs->p_eglDestroyContext( egl->display, context );
    return TRUE;
}

static BOOL egldrv_make_current( struct opengl_drawable *draw, struct opengl_drawable *read, void *context )
{
    const struct opengl_funcs *funcs = &display_funcs;
    const struct egl_platform *egl = &display_egl;

    TRACE( "draw %s, read %s, context %p\n", debugstr_opengl_drawable( draw ), debugstr_opengl_drawable( read ), context );

    return funcs->p_eglMakeCurrent( egl->display, context ? draw->surface : EGL_NO_SURFACE, context ? read->surface : EGL_NO_SURFACE, context );
}

static void egldrv_pbuffer_destroy( struct opengl_drawable *drawable )
{
    TRACE( "%s\n", debugstr_opengl_drawable( drawable ) );
}

static const struct opengl_drawable_funcs egldrv_pbuffer_funcs =
{
    .destroy = egldrv_pbuffer_destroy,
};

static const struct opengl_driver_funcs egldrv_funcs =
{
    .p_init_egl_platform = egldrv_init_egl_platform,
    .p_get_proc_address = egldrv_get_proc_address,
    .p_init_pixel_formats = egldrv_init_pixel_formats,
    .p_describe_pixel_format = egldrv_describe_pixel_format,
    .p_init_wgl_extensions = egldrv_init_wgl_extensions,
    .p_surface_create = egldrv_surface_create,
    .p_pbuffer_create = egldrv_pbuffer_create,
    .p_pbuffer_updated = egldrv_pbuffer_updated,
    .p_pbuffer_bind = egldrv_pbuffer_bind,
    .p_context_create = egldrv_context_create,
    .p_context_destroy = egldrv_context_destroy,
    .p_make_current = egldrv_make_current,
};

static BOOL egl_init( const struct opengl_driver_funcs **driver_funcs )
{
    struct opengl_funcs *funcs = &display_funcs;
    const char *extensions;

    if (!(funcs->egl_handle = dlopen( SONAME_LIBEGL, RTLD_NOW | RTLD_GLOBAL )))
    {
        ERR( "Failed to load %s: %s\n", SONAME_LIBEGL, dlerror() );
        return FALSE;
    }

#define LOAD_FUNCPTR( name )                                    \
    if (!(funcs->p_##name = dlsym( funcs->egl_handle, #name ))) \
    {                                                           \
        ERR( "Failed to find EGL function %s\n", #name );       \
        goto failed;                                            \
    }
    LOAD_FUNCPTR( eglGetProcAddress );
    LOAD_FUNCPTR( eglQueryString );
#undef LOAD_FUNCPTR

    if (!(extensions = funcs->p_eglQueryString( EGL_NO_DISPLAY, EGL_EXTENSIONS )))
    {
        ERR( "Failed to find client extensions\n" );
        goto failed;
    }
    TRACE( "EGL client extensions:\n" );
    dump_extensions( extensions );

#define CHECK_EXTENSION( ext )                                  \
    if (!has_extension( extensions, #ext ))                     \
    {                                                           \
        ERR( "Failed to find required extension %s\n", #ext );  \
        goto failed;                                            \
    }
    CHECK_EXTENSION( EGL_KHR_client_get_all_proc_addresses );
    CHECK_EXTENSION( EGL_EXT_platform_base );
#undef CHECK_EXTENSION

#define USE_GL_FUNC( func )                                                                     \
    if (!funcs->p_##func && !(funcs->p_##func = (void *)funcs->p_eglGetProcAddress( #func )))   \
    {                                                                                           \
        ERR( "Failed to load symbol %s\n", #func );                                             \
        goto failed;                                                                            \
    }
    ALL_EGL_FUNCS
#undef USE_GL_FUNC

    *driver_funcs = &egldrv_funcs;
    return TRUE;

failed:
    dlclose( funcs->egl_handle );
    funcs->egl_handle = NULL;
    return FALSE;
}

static void init_egl_platform( struct egl_platform *egl, struct opengl_funcs *funcs,
                               const struct opengl_driver_funcs *driver_funcs )
{
    const char *extensions;
    EGLint major, minor;

    if (!funcs->egl_handle || !driver_funcs->p_init_egl_platform) return;

    driver_funcs->p_init_egl_platform( egl );
    if (!egl->type) egl->display = funcs->p_eglGetDisplay( EGL_DEFAULT_DISPLAY );
    else egl->display = funcs->p_eglGetPlatformDisplay( egl->type, egl->native_display, NULL );

    if (!egl->display)
    {
        ERR( "Failed to open EGL display\n" );
        return;
    }

    if (!funcs->p_eglInitialize( egl->display, &major, &minor )) return;
    TRACE( "Initialized EGL display %p, version %d.%d\n", egl->display, major, minor );

    if (!(extensions = funcs->p_eglQueryString( egl->display, EGL_EXTENSIONS ))) return;
    TRACE( "EGL display extensions:\n" );
    dump_extensions( extensions );

#define CHECK_EXTENSION( ext )                                                                     \
    if (!has_extension( extensions, #ext ))                                                        \
    {                                                                                              \
        ERR( "Failed to find required extension %s\n", #ext );                                     \
        return;                                                                                    \
    }
    CHECK_EXTENSION( EGL_KHR_create_context );
    CHECK_EXTENSION( EGL_KHR_create_context_no_error );
    CHECK_EXTENSION( EGL_KHR_no_config_context );
#undef CHECK_EXTENSION

    egl->has_EGL_EXT_present_opaque = has_extension( extensions, "EGL_EXT_present_opaque" );
    egl->has_EGL_EXT_pixel_format_float = has_extension( extensions, "EGL_EXT_pixel_format_float" );
}

#else /* SONAME_LIBEGL */

static BOOL egl_init( const struct opengl_driver_funcs **driver_funcs )
{
    WARN( "EGL support not compiled in!\n" );
    return FALSE;
}

static void init_egl_platform( struct egl_platform *egl, struct opengl_funcs *funcs,
                               const struct opengl_driver_funcs *driver_funcs )
{
}

#endif /* SONAME_LIBEGL */

static const struct
{
    BYTE color_bits;
    BYTE red_bits, red_shift;
    BYTE green_bits, green_shift;
    BYTE blue_bits, blue_shift;
    BYTE alpha_bits, alpha_shift;
    BYTE accum_bits;
    BYTE depth_bits;
    BYTE stencil_bits;
} nulldrv_pixel_formats[] =
{
    { 32,  8, 16, 8, 8,  8, 0,  8, 24,  16, 32, 8 },
    { 32,  8, 16, 8, 8,  8, 0,  8, 24,  16, 16, 8 },
    { 32,  8, 0,  8, 8,  8, 16, 8, 24,  16, 32, 8 },
    { 32,  8, 0,  8, 8,  8, 16, 8, 24,  16, 16, 8 },
    { 32,  8, 8,  8, 16, 8, 24, 8, 0,   16, 32, 8 },
    { 32,  8, 8,  8, 16, 8, 24, 8, 0,   16, 16, 8 },
    { 24,  8, 0,  8, 8,  8, 16, 0, 0,   16, 32, 8 },
    { 24,  8, 0,  8, 8,  8, 16, 0, 0,   16, 16, 8 },
    { 24,  8, 16, 8, 8,  8, 0,  0, 0,   16, 32, 8 },
    { 24,  8, 16, 8, 8,  8, 0,  0, 0,   16, 16, 8 },
    { 16,  5, 0,  6, 5,  5, 11, 0, 0,   16, 32, 8 },
    { 16,  5, 0,  6, 5,  5, 11, 0, 0,   16, 16, 8 },
};

static void *nulldrv_get_proc_address( const char *name )
{
    return NULL;
}

static UINT nulldrv_init_pixel_formats( UINT *onscreen_count )
{
    *onscreen_count = ARRAY_SIZE(nulldrv_pixel_formats);
    return ARRAY_SIZE(nulldrv_pixel_formats);
}

static BOOL nulldrv_describe_pixel_format( int format, struct wgl_pixel_format *descr )
{
    if (format <= 0 || format > ARRAY_SIZE(nulldrv_pixel_formats)) return FALSE;

    memset( descr, 0, sizeof(*descr) );
    descr->pfd.nSize            = sizeof(*descr);
    descr->pfd.nVersion         = 1;
    descr->pfd.dwFlags          = PFD_SUPPORT_GDI | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_BITMAP | PFD_GENERIC_FORMAT;
    descr->pfd.iPixelType       = PFD_TYPE_RGBA;
    descr->pfd.cColorBits       = nulldrv_pixel_formats[format - 1].color_bits;
    descr->pfd.cRedBits         = nulldrv_pixel_formats[format - 1].red_bits;
    descr->pfd.cRedShift        = nulldrv_pixel_formats[format - 1].red_shift;
    descr->pfd.cGreenBits       = nulldrv_pixel_formats[format - 1].green_bits;
    descr->pfd.cGreenShift      = nulldrv_pixel_formats[format - 1].green_shift;
    descr->pfd.cBlueBits        = nulldrv_pixel_formats[format - 1].blue_bits;
    descr->pfd.cBlueShift       = nulldrv_pixel_formats[format - 1].blue_shift;
    descr->pfd.cAlphaBits       = nulldrv_pixel_formats[format - 1].alpha_bits;
    descr->pfd.cAlphaShift      = nulldrv_pixel_formats[format - 1].alpha_shift;
    descr->pfd.cAccumBits       = nulldrv_pixel_formats[format - 1].accum_bits;
    descr->pfd.cAccumRedBits    = nulldrv_pixel_formats[format - 1].accum_bits / 4;
    descr->pfd.cAccumGreenBits  = nulldrv_pixel_formats[format - 1].accum_bits / 4;
    descr->pfd.cAccumBlueBits   = nulldrv_pixel_formats[format - 1].accum_bits / 4;
    descr->pfd.cAccumAlphaBits  = nulldrv_pixel_formats[format - 1].accum_bits / 4;
    descr->pfd.cDepthBits       = nulldrv_pixel_formats[format - 1].depth_bits;
    descr->pfd.cStencilBits     = nulldrv_pixel_formats[format - 1].stencil_bits;
    descr->pfd.cAuxBuffers      = 0;
    descr->pfd.iLayerType       = PFD_MAIN_PLANE;

    return TRUE;
}

static const char *nulldrv_init_wgl_extensions( struct opengl_funcs *funcs )
{
    return "";
}

static BOOL nulldrv_surface_create( HWND hwnd, int format, struct opengl_drawable **drawable )
{
    return TRUE;
}

static BOOL nulldrv_pbuffer_create( HDC hdc, int format, BOOL largest, GLenum texture_format, GLenum texture_target,
                                    GLint max_level, GLsizei *width, GLsizei *height, struct opengl_drawable **drawable )
{
    return FALSE;
}

static BOOL nulldrv_pbuffer_updated( HDC hdc, struct opengl_drawable *drawable, GLenum cube_face, GLint mipmap_level )
{
    return GL_TRUE;
}

static UINT nulldrv_pbuffer_bind( HDC hdc, struct opengl_drawable *drawable, GLenum buffer )
{
    return -1; /* use default implementation */
}

static BOOL nulldrv_context_create( int format, void *share, const int *attribs, void **private )
{
    return FALSE;
}

static BOOL nulldrv_context_destroy( void *private )
{
    return FALSE;
}

static BOOL nulldrv_make_current( struct opengl_drawable *draw_base, struct opengl_drawable *read_base, void *private )
{
    return FALSE;
}

static const struct opengl_driver_funcs nulldrv_funcs =
{
    .p_get_proc_address = nulldrv_get_proc_address,
    .p_init_pixel_formats = nulldrv_init_pixel_formats,
    .p_describe_pixel_format = nulldrv_describe_pixel_format,
    .p_init_wgl_extensions = nulldrv_init_wgl_extensions,
    .p_surface_create = nulldrv_surface_create,
    .p_pbuffer_create = nulldrv_pbuffer_create,
    .p_pbuffer_updated = nulldrv_pbuffer_updated,
    .p_pbuffer_bind = nulldrv_pbuffer_bind,
    .p_context_create = nulldrv_context_create,
    .p_context_destroy = nulldrv_context_destroy,
    .p_make_current = nulldrv_make_current,
};

static const char *win32u_wglGetExtensionsStringARB( HDC hdc )
{
    TRACE( "hdc %p\n", hdc );
    if (TRACE_ON(wgl)) dump_extensions( wgl_extensions );
    return wgl_extensions;
}

static const char *win32u_wglGetExtensionsStringEXT(void)
{
    TRACE( "\n" );
    if (TRACE_ON(wgl)) dump_extensions( wgl_extensions );
    return wgl_extensions;
}

static int get_dc_pixel_format( HDC hdc, BOOL internal )
{
    int ret = 0;
    HWND hwnd;
    DC *dc;

    if ((hwnd = NtUserWindowFromDC( hdc )))
        ret = get_window_pixel_format( hwnd, internal );
    else if ((dc = get_dc_ptr( hdc )))
    {
        BOOL is_display = dc->is_display;
        ret = dc->pixel_format;
        release_dc_ptr( dc );

        /* Offscreen formats can't be used with traditional WGL calls. As has been
         * verified on Windows GetPixelFormat doesn't fail but returns 1.
         */
        if (is_display && ret >= 0 && ret > onscreen_count) ret = 1;
    }
    else
    {
        WARN( "Invalid DC handle %p\n", hdc );
        RtlSetLastWin32Error( ERROR_INVALID_HANDLE );
        return -1;
    }

    TRACE( "%p/%p -> %d\n", hdc, hwnd, ret );
    return ret;
}

static int win32u_wglGetPixelFormat( HDC hdc )
{
    int format = get_dc_pixel_format( hdc, FALSE );
    return format > 0 ? format : 0;
}

void set_window_opengl_drawable( HWND hwnd, struct opengl_drawable *new_drawable )
{
    void *old_drawable = NULL;
    WND *win;

    TRACE( "hwnd %p, new_drawable %s\n", hwnd, debugstr_opengl_drawable( new_drawable ) );

    if ((win = get_win_ptr( hwnd )) && win != WND_DESKTOP && win != WND_OTHER_PROCESS)
    {
        old_drawable = win->opengl_drawable;
        if ((win->opengl_drawable = new_drawable)) opengl_drawable_add_ref( new_drawable );
        release_win_ptr( win );
    }

    if (old_drawable) opengl_drawable_release( old_drawable );
}

struct opengl_drawable *get_window_opengl_drawable( HWND hwnd )
{
    void *drawable = NULL;
    WND *win;

    if ((win = get_win_ptr( hwnd )) && win != WND_DESKTOP && win != WND_OTHER_PROCESS)
    {
        if ((drawable = win->opengl_drawable)) opengl_drawable_add_ref( drawable );
        release_win_ptr( win );
    }

    TRACE( "hwnd %p, drawable %s\n", hwnd, debugstr_opengl_drawable( drawable ) );
    return drawable;
}

void set_dc_opengl_drawable( HDC hdc, struct opengl_drawable *new_drawable )
{
    void *old_drawable = NULL;
    DC *dc;

    TRACE( "hdc %p, new_drawable %s\n", hdc, debugstr_opengl_drawable( new_drawable ) );

    if ((dc = get_dc_ptr( hdc )))
    {
        old_drawable = dc->opengl_drawable;
        if ((dc->opengl_drawable = new_drawable)) opengl_drawable_add_ref( new_drawable );
        release_dc_ptr( dc );
    }

    if (old_drawable) opengl_drawable_release( old_drawable );
}

static struct opengl_drawable *get_dc_opengl_drawable( HDC hdc )
{
    void *drawable = NULL;
    DC *dc;

    if ((dc = get_dc_ptr( hdc )))
    {
        if ((drawable = dc->opengl_drawable)) opengl_drawable_add_ref( drawable );
        release_dc_ptr( dc );
    }

    TRACE( "hdc %p, drawable %s\n", hdc, debugstr_opengl_drawable( drawable ) );
    return drawable;
}

static BOOL create_memory_pbuffer( HDC hdc, int format )
{
    const struct opengl_funcs *funcs = &display_funcs;
    dib_info dib = {.rect = {0, 0, 1, 1}};
    BOOL ret = TRUE;
    BITMAPOBJ *bmp;
    DC *dc;

    if (!(dc = get_dc_ptr( hdc ))) return FALSE;
    else if (dc->opengl_drawable) ret = FALSE;
    else if (get_gdi_object_type( hdc ) != NTGDI_OBJ_MEMDC) ret = FALSE;
    else if ((bmp = GDI_GetObjPtr( dc->hBitmap, NTGDI_OBJ_BITMAP )))
    {
        init_dib_info_from_bitmapobj( &dib, bmp );
        GDI_ReleaseObj( dc->hBitmap );
    }
    release_dc_ptr( dc );

    if (ret && format)
    {
        int width = dib.rect.right - dib.rect.left, height = dib.rect.bottom - dib.rect.top;
        struct wgl_pbuffer *pbuffer;

        if (!(pbuffer = funcs->p_wglCreatePbufferARB( hdc, format, width, height, NULL )))
            WARN( "Failed to create pbuffer for memory DC %p\n", hdc );
        else
        {
            TRACE( "Created pbuffer %p for memory DC %p\n", pbuffer, hdc );
            set_dc_opengl_drawable( hdc, pbuffer->drawable );
            funcs->p_wglDestroyPbufferARB( pbuffer );
        }
    }

    return ret;
}

static BOOL flush_memory_dc( struct wgl_context *context, HDC hdc, BOOL write, void (*flush)(void) )
{
    const struct opengl_funcs *funcs = &display_funcs;
    BOOL ret = TRUE;
    BITMAPOBJ *bmp;
    DC *dc;

    if (!(dc = get_dc_ptr( hdc ))) return FALSE;
    if (get_gdi_object_type( hdc ) != NTGDI_OBJ_MEMDC) ret = FALSE;
    else if (context && (bmp = GDI_GetObjPtr( dc->hBitmap, NTGDI_OBJ_BITMAP )))
    {
        char buffer[FIELD_OFFSET( BITMAPINFO, bmiColors[256] )];
        BITMAPINFO *info = (BITMAPINFO *)buffer;
        struct bitblt_coords src = {0};
        struct gdi_image_bits bits;

        if (flush) flush();

        if (!get_image_from_bitmap( bmp, info, &bits, &src ))
        {
            int width = info->bmiHeader.biWidth, height = info->bmiHeader.biSizeImage / 4 / width;
            if (write) funcs->p_glDrawPixels( width, height, GL_BGRA, GL_UNSIGNED_BYTE, bits.ptr );
            else funcs->p_glReadPixels( 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, bits.ptr );
        }
        GDI_ReleaseObj( dc->hBitmap );
    }
    release_dc_ptr( dc );

    return ret;
}

static BOOL set_dc_pixel_format( HDC hdc, int new_format, BOOL internal )
{
    const struct opengl_funcs *funcs = &display_funcs;
    UINT total, onscreen;
    HWND hwnd;

    funcs->p_get_pixel_formats( NULL, 0, &total, &onscreen );
    if (new_format <= 0 || new_format > total) return FALSE;

    if ((hwnd = NtUserWindowFromDC( hdc )))
    {
        struct opengl_drawable *drawable;
        int old_format;
        BOOL ret;

        if (new_format > onscreen)
        {
            WARN( "Invalid format %d for %p/%p\n", new_format, hdc, hwnd );
            return FALSE;
        }

        TRACE( "%p/%p format %d, internal %u\n", hdc, hwnd, new_format, internal );

        if ((old_format = get_window_pixel_format( hwnd, FALSE )) && !internal) return old_format == new_format;

        drawable = get_dc_opengl_drawable( hdc );
        if ((ret = driver_funcs->p_surface_create( hwnd, new_format, &drawable )))
        {
            /* update the current window drawable to the last used draw surface */
            if ((hwnd = NtUserWindowFromDC( hdc ))) set_window_opengl_drawable( hwnd, drawable );
            set_dc_opengl_drawable( hdc, drawable );
        }
        if (drawable) opengl_drawable_release( drawable );

        if (!ret) return FALSE;
        return set_window_pixel_format( hwnd, new_format, internal );
    }

    TRACE( "%p/%p format %d, internal %u\n", hdc, hwnd, new_format, internal );
    return NtGdiSetPixelFormat( hdc, new_format );
}

static BOOL win32u_wglSetPixelFormat( HDC hdc, int format, const PIXELFORMATDESCRIPTOR *pfd )
{
    return set_dc_pixel_format( hdc, format, FALSE );
}

static BOOL win32u_wglSetPixelFormatWINE( HDC hdc, int format )
{
    return set_dc_pixel_format( hdc, format, TRUE );
}

static PROC win32u_wglGetProcAddress( const char *name )
{
    PROC ret;
    if (!strncmp( name, "wgl", 3 )) return NULL;
    ret = driver_funcs->p_get_proc_address( name );
    TRACE( "%s -> %p\n", debugstr_a(name), ret );
    return ret;
}

static void win32u_get_pixel_formats( struct wgl_pixel_format *formats, UINT max_formats,
                                      UINT *num_formats, UINT *num_onscreen_formats )
{
    memcpy( formats, pixel_formats, min( max_formats, formats_count ) * sizeof(*pixel_formats) );
    *num_formats = formats_count;
    *num_onscreen_formats = onscreen_count;
}

static void context_set_drawables( struct wgl_context *context, struct opengl_drawable *new_draw, struct opengl_drawable *new_read )
{
    struct opengl_drawable *old_draw = context->draw, *old_read = context->read;

    TRACE( "context %p new_draw %s new_read %s\n", context, debugstr_opengl_drawable(new_draw), debugstr_opengl_drawable(new_read) );

    if ((context->draw = new_draw)) opengl_drawable_add_ref( new_draw );
    if ((context->read = new_read)) opengl_drawable_add_ref( new_read );
    if (old_draw) opengl_drawable_release( old_draw );
    if (old_read) opengl_drawable_release( old_read );
}

static BOOL context_unset_current( struct wgl_context *context )
{
    struct opengl_drawable *old_draw = context->draw, *old_read = context->read;

    TRACE( "context %p\n", context );

    opengl_drawable_flush( old_read, old_read->interval, GL_FLUSH_WAS_CURRENT );
    if (old_read != old_draw) opengl_drawable_flush( old_draw, old_draw->interval, GL_FLUSH_WAS_CURRENT );
    if (driver_funcs->p_make_current( NULL, NULL, NULL )) return TRUE;
    opengl_drawable_flush( old_read, old_read->interval, GL_FLUSH_SET_CURRENT );
    if (old_read != old_draw) opengl_drawable_flush( old_draw, old_draw->interval, GL_FLUSH_SET_CURRENT );

    return FALSE;
}

static BOOL context_sync_drawables( struct wgl_context *context, HDC draw_hdc, HDC read_hdc )
{
    struct wgl_context *previous = NtCurrentTeb()->glContext;
    struct opengl_drawable *new_draw, *new_read;
    BOOL ret = FALSE, flush;
    HWND hwnd;

    flush = create_memory_pbuffer( draw_hdc, context->format );
    new_draw = get_dc_opengl_drawable( draw_hdc );

    /* get the last used window drawable when reading */
    if ((hwnd = NtUserWindowFromDC( read_hdc ))) new_read = get_window_opengl_drawable( hwnd );
    else new_read = get_dc_opengl_drawable( read_hdc );

    TRACE( "context %p, new_draw %s, new_read %s\n", context, debugstr_opengl_drawable( new_draw ), debugstr_opengl_drawable( new_read ) );

    if (!new_draw || !new_read)
    {
        WARN( "One of the drawable has been lost, ignoring\n" );
        return FALSE;
    }

    if (previous == context && new_draw == context->draw && new_read == context->read) ret = TRUE;
    else if (previous)
    {
        struct opengl_drawable *old_draw = previous->draw, *old_read = previous->read;
        opengl_drawable_flush( old_read, old_read->interval, GL_FLUSH_WAS_CURRENT );
        if (old_read != old_draw) opengl_drawable_flush( old_draw, old_draw->interval, GL_FLUSH_WAS_CURRENT );
    }

    if (!ret && (ret = driver_funcs->p_make_current( new_draw, new_read, context->driver_private )))
    {
        NtCurrentTeb()->glContext = context;
        context_set_drawables( context, new_draw, new_read );
        if (previous && previous != context) context_set_drawables( previous, NULL, NULL );

        opengl_drawable_flush( new_read, new_read->interval, GL_FLUSH_SET_CURRENT );
        if (new_read != new_draw) opengl_drawable_flush( new_draw, new_draw->interval, GL_FLUSH_SET_CURRENT );
    }

    if (ret)
    {
        opengl_drawable_flush( new_read, new_read->interval, 0 );
        opengl_drawable_flush( new_draw, new_draw->interval, 0 );
        /* update the current window drawable to the last used draw surface */
        if ((hwnd = NtUserWindowFromDC( draw_hdc ))) set_window_opengl_drawable( hwnd, new_draw );
        if (flush) flush_memory_dc( context, draw_hdc, TRUE, NULL );
    }
    else if (previous)
    {
        struct opengl_drawable *old_draw = previous->draw, *old_read = previous->read;
        opengl_drawable_flush( old_read, old_read->interval, GL_FLUSH_SET_CURRENT );
        if (old_read != old_draw) opengl_drawable_flush( old_draw, old_draw->interval, GL_FLUSH_SET_CURRENT );
    }

    opengl_drawable_release( new_draw );
    opengl_drawable_release( new_read );
    return ret;
}

static void push_internal_context( struct wgl_context *context, HDC hdc, int format )
{
    TRACE( "context %p, hdc %p\n", context, hdc );

    if (!context->internal_context)
    {
        driver_funcs->p_context_create( format, context->driver_private, NULL, &context->internal_context );
        if (!context->internal_context) ERR( "Failed to create internal context\n" );
    }

    driver_funcs->p_make_current( context->draw, context->read, context->internal_context );
}

static void pop_internal_context( struct wgl_context *context )
{
    TRACE( "context %p\n", context );
    driver_funcs->p_make_current( context->draw, context->read, context->driver_private );
}

static BOOL win32u_wglMakeContextCurrentARB( HDC draw_hdc, HDC read_hdc, struct wgl_context *context )
{
    struct wgl_context *prev_context = NtCurrentTeb()->glContext;
    int format;

    TRACE( "draw_hdc %p, read_hdc %p, context %p\n", draw_hdc, read_hdc, context );

    if (!context)
    {
        if (!(context = prev_context)) return TRUE;
        if (!context_unset_current( context )) return FALSE;
        NtCurrentTeb()->glContext = NULL;
        context_set_drawables( context, NULL, NULL );
        return TRUE;
    }

    if ((format = get_dc_pixel_format( draw_hdc, TRUE )) <= 0)
    {
        WARN( "Invalid draw_hdc %p format %u\n", draw_hdc, format );
        if (!format) RtlSetLastWin32Error( ERROR_INVALID_PIXEL_FORMAT );
        return FALSE;
    }
    if (context->format != format)
    {
        WARN( "Mismatched draw_hdc %p format %u, context %p format %u\n", draw_hdc, format, context, context->format );
        RtlSetLastWin32Error( ERROR_INVALID_PIXEL_FORMAT );
        return FALSE;
    }

    if (!context_sync_drawables( context, draw_hdc, read_hdc )) return FALSE;
    NtCurrentTeb()->glContext = context;
    return TRUE;
}

static BOOL win32u_wglMakeCurrent( HDC hdc, struct wgl_context *context )
{
    return win32u_wglMakeContextCurrentARB( hdc, hdc, context );
}

static struct wgl_pbuffer *win32u_wglCreatePbufferARB( HDC hdc, int format, int width, int height,
                                                       const int *attribs )
{
    const struct opengl_funcs *funcs = &display_funcs;
    UINT total, onscreen, size, max_level = 0;
    struct wgl_pbuffer *pbuffer;
    BOOL largest = FALSE;

    TRACE( "(%p, %d, %d, %d, %p)\n", hdc, format, width, height, attribs );

    funcs->p_get_pixel_formats( NULL, 0, &total, &onscreen );
    if (format <= 0 || format > total)
    {
        RtlSetLastWin32Error( ERROR_INVALID_PIXEL_FORMAT );
        return NULL;
    }
    if (width <= 0 || height <= 0)
    {
        RtlSetLastWin32Error( ERROR_INVALID_DATA );
        return NULL;
    }

    if (!(pbuffer = calloc( 1, sizeof(*pbuffer) )) || !(pbuffer->hdc = NtGdiOpenDCW( NULL, NULL, NULL, 0, TRUE, NULL, NULL, NULL )))
    {
        RtlSetLastWin32Error( ERROR_NO_SYSTEM_RESOURCES );
        free( pbuffer );
        return NULL;
    }
    NtGdiSetPixelFormat( pbuffer->hdc, format );
    pbuffer->width = width;
    pbuffer->height = height;
    pbuffer->mipmap_level = -1;

    for (; attribs && attribs[0]; attribs += 2)
    {
        switch (attribs[0])
        {
        case WGL_PBUFFER_LARGEST_ARB:
            TRACE( "WGL_PBUFFER_LARGEST_ARB %#x\n", attribs[1] );
            largest = !!attribs[1];
            break;

        case WGL_TEXTURE_FORMAT_ARB:
            TRACE( "WGL_TEXTURE_FORMAT_ARB %#x\n", attribs[1] );
            switch (attribs[1])
            {
            case WGL_NO_TEXTURE_ARB:
                pbuffer->texture_format = 0;
                break;
            case WGL_TEXTURE_RGB_ARB:
                pbuffer->texture_format = GL_RGB;
                break;
            case WGL_TEXTURE_RGBA_ARB:
                pbuffer->texture_format = GL_RGBA;
                break;
            /* WGL_FLOAT_COMPONENTS_NV */
            case WGL_TEXTURE_FLOAT_R_NV:
                pbuffer->texture_format = GL_FLOAT_R_NV;
                break;
            case WGL_TEXTURE_FLOAT_RG_NV:
                pbuffer->texture_format = GL_FLOAT_RG_NV;
                break;
            case WGL_TEXTURE_FLOAT_RGB_NV:
                pbuffer->texture_format = GL_FLOAT_RGB_NV;
                break;
            case WGL_TEXTURE_FLOAT_RGBA_NV:
                pbuffer->texture_format = GL_FLOAT_RGBA_NV;
                break;
            default:
                FIXME( "Unknown texture format: %x\n", attribs[1] );
                goto failed;
            }
            break;

        case WGL_TEXTURE_TARGET_ARB:
            TRACE( "WGL_TEXTURE_TARGET_ARB %#x\n", attribs[1] );
            switch (attribs[1])
            {
            case WGL_NO_TEXTURE_ARB:
                pbuffer->texture_target = 0;
                break;
            case WGL_TEXTURE_CUBE_MAP_ARB:
                if (width != height) goto failed;
                pbuffer->texture_target = GL_TEXTURE_CUBE_MAP;
                break;
            case WGL_TEXTURE_1D_ARB:
                if (height != 1) goto failed;
                pbuffer->texture_target = GL_TEXTURE_1D;
                break;
            case WGL_TEXTURE_2D_ARB:
                pbuffer->texture_target = GL_TEXTURE_2D;
                break;
            case WGL_TEXTURE_RECTANGLE_NV:
                pbuffer->texture_target = GL_TEXTURE_RECTANGLE_NV;
                break;
            default:
                FIXME( "Unknown texture target: %x\n", attribs[1] );
                goto failed;
            }
            break;

        case WGL_MIPMAP_TEXTURE_ARB:
            TRACE( "WGL_MIPMAP_TEXTURE_ARB %#x\n", attribs[1] );
            if (attribs[1])
            {
                pbuffer->mipmap_level = max_level = 0;
                for (size = min( width, height ) / 2; size; size /= 2) max_level++;
            }
            break;

        default:
            WARN( "attribute %#x %#x not handled\n", attribs[0], attribs[1] );
            break;
        }
    }

    if (driver_funcs->p_pbuffer_create( pbuffer->hdc, format, largest, pbuffer->texture_format,
                                        pbuffer->texture_target, max_level, &pbuffer->width,
                                        &pbuffer->height, &pbuffer->drawable ))
    {
        set_dc_opengl_drawable( pbuffer->hdc, pbuffer->drawable );
        return pbuffer;
    }

failed:
    RtlSetLastWin32Error( ERROR_INVALID_DATA );
    NtGdiDeleteObjectApp( pbuffer->hdc );
    free( pbuffer );
    return NULL;
}

static BOOL win32u_wglDestroyPbufferARB( struct wgl_pbuffer *pbuffer )
{
    TRACE( "pbuffer %p\n", pbuffer );

    opengl_drawable_release( pbuffer->drawable );
    NtGdiDeleteObjectApp( pbuffer->hdc );
    free( pbuffer );

    return GL_TRUE;
}

static HDC win32u_wglGetPbufferDCARB( struct wgl_pbuffer *pbuffer )
{
    TRACE( "pbuffer %p\n", pbuffer );
    return pbuffer->hdc;
}

static int win32u_wglReleasePbufferDCARB( struct wgl_pbuffer *pbuffer, HDC hdc )
{
    TRACE( "pbuffer %p, hdc %p\n", pbuffer, hdc );

    if (hdc != pbuffer->hdc)
    {
        RtlSetLastWin32Error( ERROR_DC_NOT_FOUND );
        return FALSE;
    }

    return TRUE;
}

static BOOL win32u_wglQueryPbufferARB( struct wgl_pbuffer *pbuffer, int attrib, int *value )
{
    TRACE( "pbuffer %p, attrib %#x, value %p\n", pbuffer, attrib, value );

    switch (attrib)
    {
    case WGL_PBUFFER_WIDTH_ARB:
        *value = pbuffer->width;
        break;
    case WGL_PBUFFER_HEIGHT_ARB:
        *value = pbuffer->height;
        break;
    case WGL_PBUFFER_LOST_ARB:
        *value = GL_FALSE;
        break;

    case WGL_TEXTURE_FORMAT_ARB:
        switch (pbuffer->texture_format)
        {
        case 0: *value = WGL_NO_TEXTURE_ARB; break;
        case GL_RGB: *value = WGL_TEXTURE_RGB_ARB; break;
        case GL_RGBA: *value = WGL_TEXTURE_RGBA_ARB; break;
        /* WGL_FLOAT_COMPONENTS_NV */
        case GL_FLOAT_R_NV: *value = WGL_TEXTURE_FLOAT_R_NV; break;
        case GL_FLOAT_RG_NV: *value = WGL_TEXTURE_FLOAT_RG_NV; break;
        case GL_FLOAT_RGB_NV: *value = WGL_TEXTURE_FLOAT_RGB_NV; break;
        case GL_FLOAT_RGBA_NV: *value = WGL_TEXTURE_FLOAT_RGBA_NV; break;
        default: ERR( "Unknown texture format: %x\n", pbuffer->texture_format );
        }
        break;

    case WGL_TEXTURE_TARGET_ARB:
        switch (pbuffer->texture_target)
        {
        case 0: *value = WGL_NO_TEXTURE_ARB; break;
        case GL_TEXTURE_1D: *value = WGL_TEXTURE_1D_ARB; break;
        case GL_TEXTURE_2D: *value = WGL_TEXTURE_2D_ARB; break;
        case GL_TEXTURE_CUBE_MAP: *value = WGL_TEXTURE_CUBE_MAP_ARB; break;
        case GL_TEXTURE_RECTANGLE_NV: *value = WGL_TEXTURE_RECTANGLE_NV; break;
        }
        break;

    case WGL_MIPMAP_TEXTURE_ARB:
        *value = pbuffer->mipmap_level >= 0;
        break;
    case WGL_MIPMAP_LEVEL_ARB:
        *value = max( pbuffer->mipmap_level, 0 );
        break;
    case WGL_CUBE_MAP_FACE_ARB:
        switch (pbuffer->cube_face)
        {
        case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
        default:
            *value = WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
            break;
        case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
            *value = WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB;
            break;
        case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
            *value = WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB;
            break;
        case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
            *value = WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB;
            break;
        case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
            *value = WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB;
            break;
        case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
            *value = WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB;
            break;
        }
        break;

    default:
        FIXME( "unexpected attribute %x\n", attrib );
        break;
    }

    return GL_TRUE;
}

static GLenum binding_from_target( GLenum target )
{
    switch (target)
    {
    case GL_TEXTURE_CUBE_MAP: return GL_TEXTURE_BINDING_CUBE_MAP;
    case GL_TEXTURE_1D: return GL_TEXTURE_BINDING_1D;
    case GL_TEXTURE_2D: return GL_TEXTURE_BINDING_2D;
    case GL_TEXTURE_RECTANGLE_NV: return GL_TEXTURE_BINDING_RECTANGLE_NV;
    }
    FIXME( "Unsupported target %#x\n", target );
    return 0;
}

static BOOL win32u_wglBindTexImageARB( struct wgl_pbuffer *pbuffer, int buffer )
{
    const struct opengl_funcs *funcs = &display_funcs;
    int prev_texture = 0, format = win32u_wglGetPixelFormat( pbuffer->hdc );
    struct wgl_pixel_format desc;
    GLenum source;
    UINT ret;

    TRACE( "pbuffer %p, buffer %d\n", pbuffer, buffer );

    if (!pbuffer->texture_format)
    {
        RtlSetLastWin32Error( ERROR_INVALID_HANDLE );
        return GL_FALSE;
    }

    if (!driver_funcs->p_describe_pixel_format( format, &desc ))
    {
        RtlSetLastWin32Error( ERROR_INVALID_PIXEL_FORMAT );
        return FALSE;
    }

    switch (buffer)
    {
    case WGL_FRONT_LEFT_ARB:
        if (desc.pfd.dwFlags & PFD_STEREO) source = GL_FRONT_LEFT;
        else source = GL_FRONT;
        break;
    case WGL_FRONT_RIGHT_ARB:
        source = GL_FRONT_RIGHT;
        break;
    case WGL_BACK_LEFT_ARB:
        if (desc.pfd.dwFlags & PFD_STEREO) source = GL_BACK_LEFT;
        else source = GL_BACK;
        break;
    case WGL_BACK_RIGHT_ARB:
        source = GL_BACK_RIGHT;
        break;
    case WGL_AUX0_ARB: source = GL_AUX0; break;
    case WGL_AUX1_ARB: source = GL_AUX1; break;
    case WGL_AUX2_ARB: source = GL_AUX2; break;
    case WGL_AUX3_ARB: source = GL_AUX3; break;

    case WGL_AUX4_ARB:
    case WGL_AUX5_ARB:
    case WGL_AUX6_ARB:
    case WGL_AUX7_ARB:
    case WGL_AUX8_ARB:
    case WGL_AUX9_ARB:
        FIXME( "Unsupported source buffer %#x\n", buffer );
        RtlSetLastWin32Error( ERROR_INVALID_DATA );
        return GL_FALSE;

    default:
        WARN( "Unknown source buffer %#x\n", buffer );
        RtlSetLastWin32Error( ERROR_INVALID_DATA );
        return GL_FALSE;
    }

    if ((ret = driver_funcs->p_pbuffer_bind( pbuffer->hdc, pbuffer->drawable, source )) != -1)
        return ret;

    funcs->p_glGetIntegerv( binding_from_target( pbuffer->texture_target ), &prev_texture );
    push_internal_context( NtCurrentTeb()->glContext, pbuffer->hdc, format );

    /* Make sure that the prev_texture is set as the current texture state isn't shared
     * between contexts. After that copy the pbuffer texture data. */
    funcs->p_glBindTexture( pbuffer->texture_target, prev_texture );
    funcs->p_glCopyTexImage2D( pbuffer->texture_target, 0, pbuffer->texture_format, 0, 0,
                                        pbuffer->width, pbuffer->height, 0 );

    pop_internal_context( NtCurrentTeb()->glContext );
    return GL_TRUE;
}

static BOOL win32u_wglReleaseTexImageARB( struct wgl_pbuffer *pbuffer, int buffer )
{
    TRACE( "pbuffer %p, buffer %d\n", pbuffer, buffer );

    if (!pbuffer->texture_format)
    {
        RtlSetLastWin32Error( ERROR_INVALID_HANDLE );
        return GL_FALSE;
    }

    return !!driver_funcs->p_pbuffer_bind( pbuffer->hdc, pbuffer->drawable, GL_NONE );
}

static BOOL win32u_wglSetPbufferAttribARB( struct wgl_pbuffer *pbuffer, const int *attribs )
{
    TRACE( "pbuffer %p, attribs %p\n", pbuffer, attribs );

    if (!pbuffer->texture_format)
    {
        RtlSetLastWin32Error( ERROR_INVALID_HANDLE );
        return GL_FALSE;
    }

    for (; attribs && attribs[0]; attribs += 2)
    {
        switch (attribs[0])
        {
        case WGL_MIPMAP_LEVEL_ARB:
            TRACE( "WGL_MIPMAP_LEVEL_ARB %#x\n", attribs[1] );
            pbuffer->mipmap_level = attribs[1];
            break;

        case WGL_CUBE_MAP_FACE_ARB:
            TRACE( "WGL_CUBE_MAP_FACE_ARB %#x\n", attribs[1] );
            switch (attribs[1])
            {
            case WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB:
                pbuffer->cube_face = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
                break;
            case WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB:
                pbuffer->cube_face = GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
                break;
            case WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB:
                pbuffer->cube_face = GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
                break;
            case WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB:
                pbuffer->cube_face = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
                break;
            case WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB:
                pbuffer->cube_face = GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
                break;
            case WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB:
                pbuffer->cube_face = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
                break;
            default:
                FIXME( "Unknown texture face: %x\n", attribs[1] );
                RtlSetLastWin32Error( ERROR_INVALID_DATA );
                return GL_FALSE;
            }
            break;

        default:
            FIXME( "Invalid attribute 0x%x\n", attribs[0] );
            RtlSetLastWin32Error( ERROR_INVALID_DATA );
            return GL_FALSE;
        }
    }

    return driver_funcs->p_pbuffer_updated( pbuffer->hdc, pbuffer->drawable, pbuffer->cube_face,
                                            max( pbuffer->mipmap_level, 0 ) );
}

static int get_window_swap_interval( HWND hwnd )
{
    int interval;
    WND *win;

    if (!(win = get_win_ptr( hwnd )) || win == WND_DESKTOP || win == WND_OTHER_PROCESS) return 0;
    interval = win->swap_interval;
    release_win_ptr( win );

    return interval;
}

static BOOL win32u_wgl_context_reset( struct wgl_context *context, HDC hdc, struct wgl_context *share, const int *attribs )
{
    void *share_private = share ? share->driver_private : NULL;
    int format;

    TRACE( "context %p, hdc %p, share %p, attribs %p\n", context, hdc, share, attribs );

    if (context->internal_context)
    {
        driver_funcs->p_context_destroy( context->internal_context );
        context->internal_context = NULL;
    }
    if (context->driver_private && !driver_funcs->p_context_destroy( context->driver_private ))
    {
        WARN( "Failed to destroy driver context %p\n", context->driver_private );
        return FALSE;
    }
    context->driver_private = NULL;
    if (!hdc) return TRUE;

    if ((format = get_dc_pixel_format( hdc, TRUE )) <= 0)
    {
        if (!format) RtlSetLastWin32Error( ERROR_INVALID_PIXEL_FORMAT );
        return FALSE;
    }
    if (!driver_funcs->p_context_create( format, share_private, attribs, &context->driver_private ))
    {
        WARN( "Failed to create driver context for context %p\n", context );
        return FALSE;
    }
    context->format = format;

    TRACE( "reset context %p, format %u for driver context %p\n", context, format, context->driver_private );
    return TRUE;
}

static BOOL win32u_wgl_context_flush( struct wgl_context *context, void (*flush)(void) )
{
    HDC draw_hdc = NtCurrentTeb()->glReserved1[0], read_hdc = NtCurrentTeb()->glReserved1[1];
    const struct opengl_funcs *funcs = &display_funcs;
    struct opengl_drawable *draw;
    UINT flags = 0;
    int interval;
    HWND hwnd;

    if (!(hwnd = NtUserWindowFromDC( draw_hdc ))) interval = 0;
    else interval = get_window_swap_interval( hwnd );

    TRACE( "context %p, hwnd %p, draw_hdc %p, interval %d, flush %p\n", context, hwnd, draw_hdc, interval, flush );

    context_sync_drawables( context, draw_hdc, read_hdc );
    if (flush_memory_dc( context, draw_hdc, FALSE, flush )) return TRUE;

    if (flush) flush();
    if (flush == funcs->p_glFinish) flags |= GL_FLUSH_FINISHED;

    if (!(draw = get_dc_opengl_drawable( draw_hdc ))) return FALSE;
    opengl_drawable_flush( draw, interval, flags );
    opengl_drawable_release( draw );

    return TRUE;
}

static BOOL win32u_wglSwapBuffers( HDC hdc )
{
    HDC draw_hdc = NtCurrentTeb()->glReserved1[0], read_hdc = NtCurrentTeb()->glReserved1[1];
    struct wgl_context *context = NtCurrentTeb()->glContext;
    const struct opengl_funcs *funcs = &display_funcs;
    struct opengl_drawable *draw;
    int interval;
    HWND hwnd;
    BOOL ret;

    if (!(hwnd = NtUserWindowFromDC( hdc ))) interval = 0;
    else interval = get_window_swap_interval( hwnd );

    context_sync_drawables( context, draw_hdc, read_hdc );
    if (flush_memory_dc( context, hdc, FALSE, funcs->p_glFlush )) return TRUE;

    if (!(draw = get_dc_opengl_drawable( draw_hdc ))) return FALSE;
    opengl_drawable_flush( draw, interval, 0 );
    if (!draw->client) ret = FALSE; /* pbuffer, nothing to do */
    else ret = draw->funcs->swap( draw );
    opengl_drawable_release( draw );

    return ret;
}

static BOOL win32u_wglSwapIntervalEXT( int interval )
{
    HDC hdc = NtCurrentTeb()->glReserved1[0];
    HWND hwnd;
    WND *win;

    if (!(hwnd = NtUserWindowFromDC( hdc )) || !(win = get_win_ptr( hwnd )))
    {
        RtlSetLastWin32Error( ERROR_DC_NOT_FOUND );
        return FALSE;
    }
    if (win == WND_DESKTOP || win == WND_OTHER_PROCESS)
    {
        WARN( "setting swap interval on win %p not supported\n", hwnd );
        return TRUE;
    }

    TRACE( "setting window %p swap interval %d\n", hwnd, interval );
    win->swap_interval = interval;
    release_win_ptr( win );
    return TRUE;
}

static int win32u_wglGetSwapIntervalEXT(void)
{
    HDC hdc = NtCurrentTeb()->glReserved1[0];
    int interval;
    HWND hwnd;
    WND *win;

    if (!(hwnd = NtUserWindowFromDC( hdc )) || !(win = get_win_ptr( hwnd )))
    {
        RtlSetLastWin32Error( ERROR_DC_NOT_FOUND );
        return 0;
    }
    if (win == WND_DESKTOP || win == WND_OTHER_PROCESS)
    {
        WARN( "setting swap interval on win %p not supported\n", hwnd );
        return TRUE;
    }
    interval = win->swap_interval;
    release_win_ptr( win );

    return interval;
}

static void display_funcs_init(void)
{
    UINT status;

    if (egl_init( &driver_funcs )) TRACE( "Initialized EGL library\n" );

    if ((status = user_driver->pOpenGLInit( WINE_OPENGL_DRIVER_VERSION, &display_funcs, &driver_funcs )))
        WARN( "Failed to initialize the driver OpenGL functions, status %#x\n", status );
    init_egl_platform( &display_egl, &display_funcs, driver_funcs );

    formats_count = driver_funcs->p_init_pixel_formats( &onscreen_count );
    if (!(pixel_formats = malloc( formats_count * sizeof(*pixel_formats) ))) ERR( "Failed to allocate memory for pixel formats\n" );
    else for (int i = 0; i < formats_count; i++) driver_funcs->p_describe_pixel_format( i + 1, pixel_formats + i );

#define USE_GL_FUNC(func) \
    if (!display_funcs.p_##func && !(display_funcs.p_##func = driver_funcs->p_get_proc_address( #func ))) \
    { \
        WARN( "%s not found for memory DCs.\n", #func ); \
        display_funcs.p_##func = default_funcs->p_##func; \
    }
    ALL_GL_FUNCS
    USE_GL_FUNC(glBindFramebuffer)
    USE_GL_FUNC(glCheckNamedFramebufferStatus)
    USE_GL_FUNC(glCreateFramebuffers)
    USE_GL_FUNC(glCreateRenderbuffers)
    USE_GL_FUNC(glDeleteFramebuffers)
    USE_GL_FUNC(glDeleteRenderbuffers)
    USE_GL_FUNC(glGetNamedFramebufferAttachmentParameteriv)
    USE_GL_FUNC(glNamedFramebufferDrawBuffer)
    USE_GL_FUNC(glNamedFramebufferReadBuffer)
    USE_GL_FUNC(glNamedFramebufferRenderbuffer)
    USE_GL_FUNC(glNamedRenderbufferStorageMultisample)
#undef USE_GL_FUNC

    display_funcs.p_wglGetProcAddress = win32u_wglGetProcAddress;
    display_funcs.p_get_pixel_formats = win32u_get_pixel_formats;

    strcpy( wgl_extensions, driver_funcs->p_init_wgl_extensions( &display_funcs ) );
    display_funcs.p_wglGetPixelFormat = win32u_wglGetPixelFormat;
    display_funcs.p_wglSetPixelFormat = win32u_wglSetPixelFormat;

    display_funcs.p_wglCreateContext = (void *)1; /* never called */
    display_funcs.p_wglDeleteContext = (void *)1; /* never called */
    display_funcs.p_wglCopyContext = (void *)1; /* never called */
    display_funcs.p_wglShareLists = (void *)1; /* never called */
    display_funcs.p_wglMakeCurrent = win32u_wglMakeCurrent;

    display_funcs.p_wglSwapBuffers = win32u_wglSwapBuffers;
    display_funcs.p_wgl_context_reset = win32u_wgl_context_reset;
    display_funcs.p_wgl_context_flush = win32u_wgl_context_flush;

    if (display_egl.has_EGL_EXT_pixel_format_float)
    {
        register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_pixel_format_float" );
        register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ATI_pixel_format_float" );
    }

    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_extensions_string" );
    display_funcs.p_wglGetExtensionsStringARB = win32u_wglGetExtensionsStringARB;

    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_EXT_extensions_string" );
    display_funcs.p_wglGetExtensionsStringEXT = win32u_wglGetExtensionsStringEXT;

    /* In WineD3D we need the ability to set the pixel format more than once (e.g. after a device reset).
     * The default wglSetPixelFormat doesn't allow this, so add our own which allows it.
     */
    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_WINE_pixel_format_passthrough" );
    display_funcs.p_wglSetPixelFormatWINE = win32u_wglSetPixelFormatWINE;

    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_pixel_format" );
    display_funcs.p_wglChoosePixelFormatARB      = (void *)1; /* never called */
    display_funcs.p_wglGetPixelFormatAttribfvARB = (void *)1; /* never called */
    display_funcs.p_wglGetPixelFormatAttribivARB = (void *)1; /* never called */

    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_create_context" );
    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_create_context_no_error" );
    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_create_context_profile" );
    display_funcs.p_wglCreateContextAttribsARB = (void *)1; /* never called */

    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_make_current_read" );
    display_funcs.p_wglGetCurrentReadDCARB   = (void *)1;  /* never called */
    display_funcs.p_wglMakeContextCurrentARB = win32u_wglMakeContextCurrentARB;

    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_pbuffer" );
    display_funcs.p_wglCreatePbufferARB    = win32u_wglCreatePbufferARB;
    display_funcs.p_wglDestroyPbufferARB   = win32u_wglDestroyPbufferARB;
    display_funcs.p_wglGetPbufferDCARB     = win32u_wglGetPbufferDCARB;
    display_funcs.p_wglReleasePbufferDCARB = win32u_wglReleasePbufferDCARB;
    display_funcs.p_wglQueryPbufferARB     = win32u_wglQueryPbufferARB;

    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_ARB_render_texture" );
    display_funcs.p_wglBindTexImageARB     = win32u_wglBindTexImageARB;
    display_funcs.p_wglReleaseTexImageARB  = win32u_wglReleaseTexImageARB;
    display_funcs.p_wglSetPbufferAttribARB = win32u_wglSetPbufferAttribARB;

    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_EXT_swap_control" );
    register_extension( wgl_extensions, ARRAY_SIZE(wgl_extensions), "WGL_EXT_swap_control_tear" );
    display_funcs.p_wglSwapIntervalEXT = win32u_wglSwapIntervalEXT;
    display_funcs.p_wglGetSwapIntervalEXT = win32u_wglGetSwapIntervalEXT;
}

/***********************************************************************
 *      __wine_get_wgl_driver  (win32u.@)
 */
const struct opengl_funcs *__wine_get_wgl_driver( HDC hdc, UINT version, const struct opengl_funcs *null_funcs )
{
    static pthread_once_t init_once = PTHREAD_ONCE_INIT;
    DWORD is_disabled, is_display, is_memdc;
    DC *dc;

    if (version != WINE_OPENGL_DRIVER_VERSION)
    {
        ERR( "version mismatch, opengl32 wants %u but dibdrv has %u\n",
             version, WINE_OPENGL_DRIVER_VERSION );
        return NULL;
    }

    InterlockedExchangePointer( (void *)&default_funcs, (void *)null_funcs );

    if (!(dc = get_dc_ptr( hdc ))) return NULL;
    is_memdc = get_gdi_object_type( hdc ) == NTGDI_OBJ_MEMDC;
    is_display = dc->is_display;
    is_disabled = dc->attr->disabled;
    release_dc_ptr( dc );

    if (is_disabled) return NULL;
    if (!is_display && !is_memdc) return NULL;
    pthread_once( &init_once, display_funcs_init );
    return &display_funcs;
}
